package com.anyext.storage.util;

import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

import org.springframework.asm.ClassWriter;
import org.springframework.asm.FieldVisitor;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.asm.commons.Method;

import com.anyext.storage.StorageContext;
import com.anyext.storage.exception.ParseException;
import com.anyext.storage.group.Group;
import com.anyext.storage.jdbc.RowCallback;
import com.anyext.storage.jdbc.RowMapper;
import com.anyext.storage.mapping.Association;
import com.anyext.storage.mapping.BaseAttribute;
import com.anyext.storage.mapping.BeanWrapper;
import com.anyext.storage.mapping.Business;
import com.anyext.storage.mapping.Collection;
import com.anyext.storage.mapping.CustomSql;
import com.anyext.storage.mapping.ResultMap;
import com.anyext.storage.mapping.RuntimeSql;
import com.anyext.storage.mapping.SqlMap;
import com.anyext.storage.mapping.impl.CustomSqlImpl;
import com.anyext.storage.policy.SqlType;
import com.anyext.storage.type.StorageSqlType;
import com.anyext.storage.util.template.MappingName;
import com.anyext.storage.util.template.MappingParam;
import com.anyext.storage.util.template.StringTemplate;

public class SqlParser extends ClassLoader implements Opcodes {

	private SqlParser() {}
	
	private static SqlParser classLoader = new SqlParser();
	
	public static String parseOrginalSql(SqlMap sqlMap) {
		StringBuffer customSql = new StringBuffer();
		String orginalSql = sqlMap.getOrginalSql().trim();
		Matcher matcher = StringUtils.ESCAPE_MARK.matcher(orginalSql);
		while (matcher.find()) {
			matcher.appendReplacement(customSql, "\\${r'" + matcher.group(1) + "'}");
		}
		matcher.appendTail(customSql);
		matcher = StringUtils.CUSTOM_MARK.matcher(customSql);
		customSql = new StringBuffer();
		int markCount = 0;
		while (matcher.find()) {
			String group = matcher.group(1);
			if ("?".equals(group)) {
				matcher.appendReplacement(customSql, "\\${mp(@" + (markCount++) + ")}");
			} else if (":".equals(group)) {
				matcher.appendReplacement(customSql, "\\${mp(" + matcher.group(2) + ")}");
			} else if ("@".equals(group)) {
				matcher.appendReplacement(customSql, "\\${mn('" + matcher.group(2) + "')}");
			} else if ("@$".equals(group)) {
				matcher.appendReplacement(customSql, "\\${mn(" + matcher.group(2) + ")}");
			}
		}
		matcher.appendTail(customSql);
		return customSql.toString();
	}
	
	static class LocalVariable {
		private int index;
		private String internalName;
		private int load;
		private int store;
		private boolean isFrameLocalVar;
		private LocalVariable(int index, String internalName, boolean isFrameLocalVar) {
			this.index = index;
			this.internalName = internalName;
			this.isFrameLocalVar = isFrameLocalVar;
		}
		protected void setVarInsn(int load, int store) {
			this.load = load;
			this.store = store;
		}
	}
	
	public static Class<?> parseRowCallback(SqlMap sqlMap) throws ParseException {
		ResultMap resultMap = sqlMap.getResultMap();

		String type = sqlMap.getParamAlias().get(resultMap.getType());
		Business business = StorageContext.getBusiness(type);
		Class<?> loadClass = null;
		try {
			loadClass = classLoader.loadClass(business.getBeanClass().getName() + "RowCallback");
		} catch (ClassNotFoundException e) {
			try {
				BeanWrapper wrapper = StorageContext.getBeanWrapper(business.getBeanClass());

				ClassWriter cw = new ClassWriter(0);
				Label label = new Label();
				FieldVisitor fv;
				MethodVisitor mv;

				int localVarIndex = 0;
				Map<String, LocalVariable> localVarMap = new LinkedHashMap<String, LocalVariable>();
				localVarMap.put("this", new LocalVariable(localVarIndex++, Type.getInternalName(business.getBeanClass()) + "RowCallback", true));
				localVarMap.get("this").setVarInsn(ALOAD, ASTORE);

				cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, localVarMap.get("this").internalName, "L" + Type.getInternalName(RowCallback.class) + "<" + Type.getDescriptor(business.getBeanClass()) + ">;", Type.getInternalName(RowCallback.class), null);
				{
					fv = cw.visitField(ACC_PRIVATE, "_businessMap", Type.getDescriptor(Map.class), "L" + Type.getInternalName(HashMap.class) + "<" + Type.getDescriptor(String.class) + Type.getDescriptor(business.getBeanClass()) + ">;", null);
					fv.visitEnd();
				}
				{
					mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
					mv.visitCode();
					mv.visitVarInsn(localVarMap.get("this").load, localVarMap.get("this").index);
					mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(RowCallback.class), "<init>", "()V");
					mv.visitVarInsn(localVarMap.get("this").load, localVarMap.get("this").index);
					mv.visitTypeInsn(NEW, Type.getInternalName(HashMap.class));
					mv.visitInsn(DUP);
					mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(HashMap.class), "<init>", "()V");
					mv.visitFieldInsn(PUTFIELD, localVarMap.get("this").internalName, "_businessMap", Type.getDescriptor(Map.class));
					mv.visitInsn(RETURN);
					mv.visitLocalVariable("this", "L" + Type.getInternalName(business.getBeanClass()) + "RowCallback;", null, label, label, 0);
					mv.visitMaxs(3, 1);
					mv.visitEnd();
				}
				{
					localVarMap.put("rs", new LocalVariable(localVarIndex++, Type.getInternalName(ResultSet.class), true));
					localVarMap.get("rs").setVarInsn(ALOAD, ASTORE);
					mv = cw.visitMethod(ACC_PUBLIC, "processRow", "(" + Type.getDescriptor(ResultSet.class) + ")V", null, new String[] { Type.getInternalName(SQLException.class) });
					mv.visitCode();

					localVarMap.put("_businessKey", new LocalVariable(localVarIndex++, Type.getInternalName(StringBuffer.class), true));
					localVarMap.get("_businessKey").setVarInsn(ALOAD, ASTORE);
					mv.visitTypeInsn(NEW, localVarMap.get("_businessKey").internalName);
					mv.visitInsn(DUP);
					mv.visitMethodInsn(INVOKESPECIAL, localVarMap.get("_businessKey").internalName, "<init>", "()V");
					mv.visitVarInsn(localVarMap.get("_businessKey").store, localVarMap.get("_businessKey").index);

					for (BaseAttribute baseAttribute : resultMap.getIds()) {
						mv.visitVarInsn(localVarMap.get("rs").load, localVarMap.get("rs").index);
						mv.visitLdcInsn(baseAttribute.getColumn());
						Class<?> sqlType = null;
						if (baseAttribute.getType() == null) {
							sqlType = wrapper.getPropertyType(baseAttribute.getProperty());
						} else {
							sqlType = StorageSqlType.getSqlType(baseAttribute.getType());
						}
						localVarMap.put(baseAttribute.getProperty(), new LocalVariable(localVarIndex++, Type.getInternalName(sqlType), true));
						Method method = null;
						if (sqlType == String.class) {
							method = Method.getMethod("String getString(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
						} else if (sqlType == Date.class) {
							method = Method.getMethod("java.sql.Timestamp getTimestamp(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
						} else if (sqlType == BigDecimal.class) {
							method = Method.getMethod("java.math.BigDecimal getBigDecimal(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
						} else if (sqlType == Boolean.TYPE || sqlType == Boolean.class) {
							method = Method.getMethod("boolean getBoolean(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
						} else if (sqlType == Byte.TYPE || sqlType == Byte.class) {
							method = Method.getMethod("byte getByte(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
						} else if (sqlType == Short.TYPE || sqlType == Short.class) {
							method = Method.getMethod("short getShort(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
						} else if (sqlType == Integer.TYPE || sqlType == Integer.class) {
							method = Method.getMethod("int getInt(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
						} else if (sqlType == Long.TYPE || sqlType == Long.class) {
							method = Method.getMethod("long getLong(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(LLOAD, LSTORE);
						} else if (sqlType == Double.TYPE || sqlType == Double.class) {
							method = Method.getMethod("double getDouble(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(DLOAD, DSTORE);
						} else if (sqlType == Float.TYPE || sqlType == Float.class) {
							method = Method.getMethod("float getFloat(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(FLOAD, FSTORE);
						} else {
							method = Method.getMethod("Object getObject(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
						}
						mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(ResultSet.class), method.getName(), method.getDescriptor());
						mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).store, localVarMap.get(baseAttribute.getProperty()).index);
						mv.visitVarInsn(localVarMap.get("_businessKey").load, localVarMap.get("_businessKey").index);
						mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).load, localVarMap.get(baseAttribute.getProperty()).index);

						method = Method.getMethod("StringBuffer append(String)");
						mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(StringBuffer.class), method.getName(), method.getDescriptor());
						mv.visitInsn(POP);
					}

					for (BaseAttribute baseAttribute : resultMap.getResults()) {
						mv.visitVarInsn(localVarMap.get("rs").load, localVarMap.get("rs").index);
						mv.visitLdcInsn(baseAttribute.getColumn());
						Class<?> sqlType = null;
						if (baseAttribute.getType() == null) {
							sqlType = wrapper.getPropertyType(baseAttribute.getProperty());
						} else {
							sqlType = StorageSqlType.getSqlType(baseAttribute.getType());
						}
						localVarMap.put(baseAttribute.getProperty(), new LocalVariable(localVarIndex++, Type.getInternalName(sqlType), true));
						Method method = null;
						if (sqlType == String.class) {
							method = Method.getMethod("String getString(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
						} else if (sqlType == Date.class) {
							method = Method.getMethod("java.sql.Timestamp getTimestamp(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
						} else if (sqlType == BigDecimal.class) {
							method = Method.getMethod("java.math.BigDecimal getBigDecimal(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
						} else if (sqlType == Boolean.TYPE || sqlType == Boolean.class) {
							method = Method.getMethod("boolean getBoolean(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
						} else if (sqlType == Byte.TYPE || sqlType == Byte.class) {
							method = Method.getMethod("byte getByte(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
						} else if (sqlType == Short.TYPE || sqlType == Short.class) {
							method = Method.getMethod("short getShort(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
						} else if (sqlType == Integer.TYPE || sqlType == Integer.class) {
							method = Method.getMethod("int getInt(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
						} else if (sqlType == Long.TYPE || sqlType == Long.class) {
							method = Method.getMethod("long getLong(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(LLOAD, LSTORE);
						} else if (sqlType == Double.TYPE || sqlType == Double.class) {
							method = Method.getMethod("double getDouble(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(DLOAD, DSTORE);
						} else if (sqlType == Float.TYPE || sqlType == Float.class) {
							method = Method.getMethod("float getFloat(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(FLOAD, FSTORE);
						} else {
							method = Method.getMethod("Object getObject(String)");
							localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
						}
						mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(ResultSet.class), method.getName(), method.getDescriptor());
						mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).store, localVarMap.get(baseAttribute.getProperty()).index);
					}

					for (Association association : resultMap.getAssociations()) {
						for (BaseAttribute baseAttribute : association.getIds()) {
							mv.visitVarInsn(localVarMap.get("rs").load, localVarMap.get("rs").index);
							mv.visitLdcInsn(baseAttribute.getColumn());
							Class<?> sqlType = null;
							if (baseAttribute.getType() == null) {
								sqlType = wrapper.getPropertyType(baseAttribute.getProperty());
							} else {
								sqlType = StorageSqlType.getSqlType(baseAttribute.getType());
							}
							localVarMap.put(baseAttribute.getProperty(), new LocalVariable(localVarIndex++, Type.getInternalName(sqlType), false));
							Method method = null;
							if (sqlType == String.class) {
								method = Method.getMethod("String getString(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == Date.class) {
								method = Method.getMethod("java.sql.Timestamp getTimestamp(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == BigDecimal.class) {
								method = Method.getMethod("java.math.BigDecimal getBigDecimal(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == Boolean.TYPE || sqlType == Boolean.class) {
								method = Method.getMethod("boolean getBoolean(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Byte.TYPE || sqlType == Byte.class) {
								method = Method.getMethod("byte getByte(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Short.TYPE || sqlType == Short.class) {
								method = Method.getMethod("short getShort(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Integer.TYPE || sqlType == Integer.class) {
								method = Method.getMethod("int getInt(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Long.TYPE || sqlType == Long.class) {
								method = Method.getMethod("long getLong(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(LLOAD, LSTORE);
							} else if (sqlType == Double.TYPE || sqlType == Double.class) {
								method = Method.getMethod("double getDouble(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(DLOAD, DSTORE);
							} else if (sqlType == Float.TYPE || sqlType == Float.class) {
								method = Method.getMethod("float getFloat(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(FLOAD, FSTORE);
							} else {
								method = Method.getMethod("Object getObject(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							}
							mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(ResultSet.class), method.getName(), method.getDescriptor());
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).store, localVarMap.get(baseAttribute.getProperty()).index);
							mv.visitVarInsn(localVarMap.get("_businessKey").load, localVarMap.get("_businessKey").index);
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).load, localVarMap.get(baseAttribute.getProperty()).index);

							method = Method.getMethod("StringBuffer append(String)");
							mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(StringBuffer.class), method.getName(), method.getDescriptor());
							mv.visitInsn(POP);
						}

						for (BaseAttribute baseAttribute : association.getResults()) {
							mv.visitVarInsn(localVarMap.get("rs").load, localVarMap.get("rs").index);
							mv.visitLdcInsn(baseAttribute.getColumn());
							Class<?> sqlType = null;
							if (baseAttribute.getType() == null) {
								sqlType = wrapper.getPropertyType(baseAttribute.getProperty());
							} else {
								sqlType = StorageSqlType.getSqlType(baseAttribute.getType());
							}
							localVarMap.put(baseAttribute.getProperty(), new LocalVariable(localVarIndex++, Type.getInternalName(sqlType), false));
							Method method = null;
							if (sqlType == String.class) {
								method = Method.getMethod("String getString(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == Date.class) {
								method = Method.getMethod("java.sql.Timestamp getTimestamp(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == BigDecimal.class) {
								method = Method.getMethod("java.math.BigDecimal getBigDecimal(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == Boolean.TYPE || sqlType == Boolean.class) {
								method = Method.getMethod("boolean getBoolean(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Byte.TYPE || sqlType == Byte.class) {
								method = Method.getMethod("byte getByte(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Short.TYPE || sqlType == Short.class) {
								method = Method.getMethod("short getShort(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Integer.TYPE || sqlType == Integer.class) {
								method = Method.getMethod("int getInt(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Long.TYPE || sqlType == Long.class) {
								method = Method.getMethod("long getLong(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(LLOAD, LSTORE);
							} else if (sqlType == Double.TYPE || sqlType == Double.class) {
								method = Method.getMethod("double getDouble(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(DLOAD, DSTORE);
							} else if (sqlType == Float.TYPE || sqlType == Float.class) {
								method = Method.getMethod("float getFloat(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(FLOAD, FSTORE);
							} else {
								method = Method.getMethod("Object getObject(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							}
							mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(ResultSet.class), method.getName(), method.getDescriptor());
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).store, localVarMap.get(baseAttribute.getProperty()).index);
						}
					}

					localVarMap.put("_businessEntity", new LocalVariable(localVarIndex++, Type.getInternalName(business.getBeanClass()), true));
					localVarMap.get("_businessEntity").setVarInsn(ALOAD, ASTORE);
					mv.visitVarInsn(localVarMap.get("this").load, localVarMap.get("this").index);
					mv.visitFieldInsn(GETFIELD, localVarMap.get("this").internalName, "_businessMap", "Ljava/util/Map;");
					mv.visitVarInsn(localVarMap.get("_businessKey").load, localVarMap.get("_businessKey").index);
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "toString", "()Ljava/lang/String;");
					mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
					mv.visitTypeInsn(CHECKCAST, Type.getInternalName(business.getBeanClass()));
					mv.visitVarInsn(localVarMap.get("_businessEntity").store, localVarMap.get("_businessEntity").index);

					mv.visitVarInsn(localVarMap.get("_businessEntity").load, localVarMap.get("_businessEntity").index);
					mv.visitJumpInsn(IFNONNULL, label);
					mv.visitTypeInsn(NEW, Type.getInternalName(business.getBeanClass()));
					mv.visitInsn(DUP);
					mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(business.getBeanClass()), "<init>", "()V");
					mv.visitVarInsn(localVarMap.get("_businessEntity").store, localVarMap.get("_businessEntity").index);

					for (BaseAttribute baseAttribute : resultMap.getIds()) {
						mv.visitVarInsn(localVarMap.get("_businessEntity").load, localVarMap.get("_businessEntity").index);
						mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).load, localVarMap.get(baseAttribute.getProperty()).index);
						Method method = Method.getMethod(wrapper.getWriteMethod(baseAttribute.getProperty()));
						mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(business.getBeanClass()), method.getName(), method.getDescriptor());
					}

					for (BaseAttribute baseAttribute : resultMap.getResults()) {
						mv.visitVarInsn(localVarMap.get("_businessEntity").load, localVarMap.get("_businessEntity").index);
						mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).load, localVarMap.get(baseAttribute.getProperty()).index);
						Method method = Method.getMethod(wrapper.getWriteMethod(baseAttribute.getProperty()));
						mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(business.getBeanClass()), method.getName(), method.getDescriptor());
					}

					for (Association association : resultMap.getAssociations()) {
						type = sqlMap.getParamAlias().get(association.getType());
						Business associationBusiness = StorageContext.getBusiness(type);
						mv.visitTypeInsn(NEW, localVarMap.get(association.getProperty()).internalName);
						mv.visitInsn(DUP);
						mv.visitMethodInsn(INVOKESPECIAL, localVarMap.get(association.getProperty()).internalName, "<init>", "()V");
						mv.visitVarInsn(localVarMap.get(association.getProperty()).store, localVarMap.get(association.getProperty()).index);

						for (BaseAttribute baseAttribute : association.getIds()) {
							mv.visitVarInsn(localVarMap.get(association.getProperty()).load, localVarMap.get(association.getProperty()).index);
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).load, localVarMap.get(baseAttribute.getProperty()).index);
							Method method = Method.getMethod(wrapper.getWriteMethod(baseAttribute.getProperty()));
							mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(associationBusiness.getBeanClass()), method.getName(), method.getDescriptor());
						}

						for (BaseAttribute baseAttribute : association.getResults()) {
							mv.visitVarInsn(localVarMap.get(association.getProperty()).load, localVarMap.get(association.getProperty()).index);
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).load, localVarMap.get(baseAttribute.getProperty()).index);
							Method method = Method.getMethod(wrapper.getWriteMethod(baseAttribute.getProperty()));
							mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(associationBusiness.getBeanClass()), method.getName(), method.getDescriptor());
						}

						mv.visitVarInsn(localVarMap.get("_businessEntity").load, localVarMap.get("_businessEntity").index);
						mv.visitVarInsn(localVarMap.get(association.getProperty()).load, localVarMap.get(association.getProperty()).index);
						Method method = Method.getMethod(wrapper.getWriteMethod(association.getProperty()));
						mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(business.getBeanClass()), method.getName(), method.getDescriptor());
					}

					mv.visitVarInsn(localVarMap.get("this").load, localVarMap.get("this").index);
					mv.visitFieldInsn(GETFIELD, localVarMap.get("this").internalName, "_businessMap", "Ljava/util/Map;");
					mv.visitVarInsn(localVarMap.get("_businessKey").load, localVarMap.get("_businessKey").index);
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "toString", "()Ljava/lang/String;");
					mv.visitVarInsn(localVarMap.get("_businessEntity").load, localVarMap.get("_businessEntity").index);
					mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
					mv.visitInsn(POP);

					mv.visitVarInsn(localVarMap.get("this").load, localVarMap.get("this").index);
					mv.visitFieldInsn(GETFIELD, localVarMap.get("this").internalName, "results", "Ljava/util/List;");
					mv.visitVarInsn(localVarMap.get("_businessEntity").load, localVarMap.get("_businessEntity").index);
					mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");
					mv.visitInsn(POP);

					List<Object> frames = new ArrayList<Object>();
					Iterator<String> iterator = localVarMap.keySet().iterator();
					while (iterator.hasNext()) {
						String next = iterator.next();
						if (localVarMap.get(next).isFrameLocalVar) {
							frames.add(localVarMap.get(next).internalName);
						}
					}
					mv.visitFrame(Opcodes.F_FULL, frames.size(), frames.toArray(), 0, new Object[] {});

					for (Collection collection : resultMap.getCollections()) {
						localVarMap.put(collection.getProperty() + "Key", new LocalVariable(localVarIndex++, Type.getInternalName(StringBuffer.class), true));
						localVarMap.get(collection.getProperty() + "Key").setVarInsn(ALOAD, ASTORE);
						mv.visitTypeInsn(NEW, localVarMap.get(collection.getProperty() + "Key").internalName);
						mv.visitInsn(DUP);
						mv.visitVarInsn(localVarMap.get("_businessKey").load, localVarMap.get("_businessKey").index);
						mv.visitMethodInsn(INVOKESPECIAL, localVarMap.get(collection.getProperty() + "Key").internalName, "<init>", "(Ljava/lang/CharSequence;)V");
						mv.visitVarInsn(localVarMap.get(collection.getProperty() + "Key").store, localVarMap.get(collection.getProperty() + "Key").index);

						for (BaseAttribute baseAttribute : collection.getIds()) {
							mv.visitVarInsn(localVarMap.get("rs").load, localVarMap.get("rs").index);
							mv.visitLdcInsn(baseAttribute.getColumn());
							Class<?> sqlType = null;
							if (baseAttribute.getType() == null) {
								sqlType = wrapper.getPropertyType(baseAttribute.getProperty());
							} else {
								sqlType = StorageSqlType.getSqlType(baseAttribute.getType());
							}
							localVarMap.put(baseAttribute.getProperty(), new LocalVariable(localVarIndex++, Type.getInternalName(sqlType), true));
							Method method = null;
							if (sqlType == String.class) {
								method = Method.getMethod("String getString(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == Date.class) {
								method = Method.getMethod("java.sql.Timestamp getTimestamp(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == BigDecimal.class) {
								method = Method.getMethod("java.math.BigDecimal getBigDecimal(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == Boolean.TYPE || sqlType == Boolean.class) {
								method = Method.getMethod("boolean getBoolean(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Byte.TYPE || sqlType == Byte.class) {
								method = Method.getMethod("byte getByte(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Short.TYPE || sqlType == Short.class) {
								method = Method.getMethod("short getShort(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Integer.TYPE || sqlType == Integer.class) {
								method = Method.getMethod("int getInt(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Long.TYPE || sqlType == Long.class) {
								method = Method.getMethod("long getLong(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(LLOAD, LSTORE);
							} else if (sqlType == Double.TYPE || sqlType == Double.class) {
								method = Method.getMethod("double getDouble(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(DLOAD, DSTORE);
							} else if (sqlType == Float.TYPE || sqlType == Float.class) {
								method = Method.getMethod("float getFloat(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(FLOAD, FSTORE);
							} else {
								method = Method.getMethod("Object getObject(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							}
							mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(ResultSet.class), method.getName(), method.getDescriptor());
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).store, localVarMap.get(baseAttribute.getProperty()).index);

							mv.visitVarInsn(localVarMap.get(collection.getProperty() + "Key").load, localVarMap.get(collection.getProperty() + "Key").index);
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).load, localVarMap.get(baseAttribute.getProperty()).index);
							method = Method.getMethod("StringBuffer append(String)");
							mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(StringBuffer.class), method.getName(), method.getDescriptor());
							mv.visitInsn(POP);
						}

						for (BaseAttribute baseAttribute : collection.getResults()) {
							mv.visitVarInsn(localVarMap.get("rs").load, localVarMap.get("rs").index);
							mv.visitLdcInsn(baseAttribute.getColumn());
							Class<?> sqlType = null;
							if (baseAttribute.getType() == null) {
								sqlType = wrapper.getPropertyType(baseAttribute.getProperty());
							} else {
								sqlType = StorageSqlType.getSqlType(baseAttribute.getType());
							}
							localVarMap.put(baseAttribute.getProperty(), new LocalVariable(localVarIndex++, Type.getInternalName(sqlType), true));
							Method method = null;
							if (sqlType == String.class) {
								method = Method.getMethod("String getString(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == Date.class) {
								method = Method.getMethod("java.sql.Timestamp getTimestamp(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == BigDecimal.class) {
								method = Method.getMethod("java.math.BigDecimal getBigDecimal(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							} else if (sqlType == Boolean.TYPE || sqlType == Boolean.class) {
								method = Method.getMethod("boolean getBoolean(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Byte.TYPE || sqlType == Byte.class) {
								method = Method.getMethod("byte getByte(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Short.TYPE || sqlType == Short.class) {
								method = Method.getMethod("short getShort(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Integer.TYPE || sqlType == Integer.class) {
								method = Method.getMethod("int getInt(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ILOAD, ISTORE);
							} else if (sqlType == Long.TYPE || sqlType == Long.class) {
								method = Method.getMethod("long getLong(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(LLOAD, LSTORE);
							} else if (sqlType == Double.TYPE || sqlType == Double.class) {
								method = Method.getMethod("double getDouble(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(DLOAD, DSTORE);
							} else if (sqlType == Float.TYPE || sqlType == Float.class) {
								method = Method.getMethod("float getFloat(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(FLOAD, FSTORE);
							} else {
								method = Method.getMethod("Object getObject(String)");
								localVarMap.get(baseAttribute.getProperty()).setVarInsn(ALOAD, ASTORE);
							}
							mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(ResultSet.class), method.getName(), method.getDescriptor());
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).store, localVarMap.get(baseAttribute.getProperty()).index);
						}

						mv.visitVarInsn(localVarMap.get("this").load, localVarMap.get("this").index);
						mv.visitFieldInsn(GETFIELD, localVarMap.get("this").internalName, "_businessMap", "Ljava/util/Map;");
						mv.visitVarInsn(localVarMap.get(collection.getProperty() + "Key").load, localVarMap.get(collection.getProperty() + "Key").index);
						mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "toString", "()Ljava/lang/String;");
						mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "containsKey", "(Ljava/lang/Object;)Z");
						mv.visitJumpInsn(IFNE, label);
						localVarMap.put(collection.getProperty() + "List", new LocalVariable(localVarIndex++, Type.getInternalName(List.class), true));
						localVarMap.get(collection.getProperty() + "List").setVarInsn(ALOAD, ASTORE);
						mv.visitVarInsn(localVarMap.get("_businessEntity").load, localVarMap.get("_businessEntity").index);
						Method method = Method.getMethod(wrapper.getReadMethod(collection.getProperty()));
						mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(business.getBeanClass()), method.getName(), method.getDescriptor());
						mv.visitVarInsn(localVarMap.get(collection.getProperty() + "List").store, localVarMap.get(collection.getProperty() + "List").index);

						mv.visitVarInsn(localVarMap.get(collection.getProperty() + "List").load, localVarMap.get(collection.getProperty() + "List").index);
						mv.visitJumpInsn(IFNONNULL, label);
						mv.visitTypeInsn(NEW, "java/util/ArrayList");
						mv.visitInsn(DUP);
						mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V");
						mv.visitVarInsn(localVarMap.get(collection.getProperty() + "List").store, localVarMap.get(collection.getProperty() + "List").index);
						mv.visitVarInsn(localVarMap.get("_businessEntity").load, localVarMap.get("_businessEntity").index);
						mv.visitVarInsn(localVarMap.get(collection.getProperty() + "List").load, localVarMap.get(collection.getProperty() + "List").index);
						method = Method.getMethod(wrapper.getWriteMethod(collection.getProperty()));
						mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(business.getBeanClass()), method.getName(), method.getDescriptor());

						frames = new ArrayList<Object>();
						iterator = localVarMap.keySet().iterator();
						while (iterator.hasNext()) {
							String next = iterator.next();
							if (localVarMap.get(next).isFrameLocalVar) {
								frames.add(localVarMap.get(next).internalName);
							}
						}
						mv.visitFrame(Opcodes.F_FULL, frames.size(), frames.toArray(), 0, new Object[] {});

						localVarMap.put(collection.getProperty(), new LocalVariable(localVarIndex++, Type.getInternalName(wrapper.getPropertyType(collection.getProperty())), false));
						localVarMap.get(collection.getProperty()).setVarInsn(ALOAD, ASTORE);
						mv.visitTypeInsn(NEW, localVarMap.get(collection.getProperty()).internalName);
						mv.visitInsn(DUP);
						mv.visitMethodInsn(INVOKESPECIAL, localVarMap.get(collection.getProperty()).internalName, "<init>", "()V");
						mv.visitVarInsn(localVarMap.get(collection.getProperty()).store, localVarMap.get(collection.getProperty()).index);

						for (BaseAttribute baseAttribute : collection.getIds()) {
							mv.visitVarInsn(localVarMap.get(collection.getProperty()).load, localVarMap.get(collection.getProperty()).index);
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).load, localVarMap.get(baseAttribute.getProperty()).index);
							method = Method.getMethod(wrapper.getWriteMethod(baseAttribute.getProperty()));
							mv.visitMethodInsn(INVOKEVIRTUAL, localVarMap.get(collection.getProperty()).internalName, method.getName(), method.getDescriptor());
						}

						for (BaseAttribute baseAttribute : collection.getResults()) {
							mv.visitVarInsn(localVarMap.get(collection.getProperty()).load, localVarMap.get(collection.getProperty()).index);
							mv.visitVarInsn(localVarMap.get(baseAttribute.getProperty()).load, localVarMap.get(baseAttribute.getProperty()).index);
							method = Method.getMethod(wrapper.getWriteMethod(baseAttribute.getProperty()));
							mv.visitMethodInsn(INVOKEVIRTUAL, localVarMap.get(collection.getProperty()).internalName, method.getName(), method.getDescriptor());
						}

						mv.visitVarInsn(localVarMap.get(collection.getProperty() + "List").load, localVarMap.get(collection.getProperty() + "List").index);
						mv.visitVarInsn(localVarMap.get(collection.getProperty()).load, localVarMap.get(collection.getProperty()).index);
						mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");
						mv.visitInsn(POP);

						mv.visitVarInsn(localVarMap.get("this").load, localVarMap.get("this").index);
						mv.visitFieldInsn(GETFIELD, localVarMap.get("this").internalName, "_businessMap", "Ljava/util/Map;");
						mv.visitVarInsn(localVarMap.get(collection.getProperty() + "Key").load, localVarMap.get(collection.getProperty() + "Key").index);
						mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "toString", "()Ljava/lang/String;");
						mv.visitInsn(ACONST_NULL);
						mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
						mv.visitInsn(POP);
						mv.visitFrame(Opcodes.F_CHOP, 1, null, 0, null);
					}

					mv.visitInsn(RETURN);
					mv.visitMaxs(3, localVarMap.size());
					mv.visitEnd();
				}
				cw.visitEnd();
				byte[] byteCode = cw.toByteArray();
				
				try {
					FileOutputStream fos = new FileOutputStream(business.getBeanClass().getSimpleName() + "RowCallback.class");
					fos.write(byteCode);
					fos.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				
				return classLoader.defineClass(business.getBeanClass().getName() + "RowCallback", byteCode, 0, byteCode.length);
			} catch (IllegalArgumentException e1) {
				e1.printStackTrace();
			} catch (ClassFormatError e1) {
				e1.printStackTrace();
			}
		}
		return loadClass;
	}

	public static RowMapper<?> parseRowMapper(Business business) throws ParseException {
		return null;
	}

	public static CustomSql parseCustomSql(SqlMap sqlMap) throws ParseException {
		Group group = sqlMap.getGroup();
		CustomSqlImpl customSql = new CustomSqlImpl();
		customSql.setIdentity(sqlMap.getIdentity());
		customSql.setParamAlias(sqlMap.getParamAlias());
		customSql.setCustomSql(parseOrginalSql(sqlMap));
		customSql.setRowCallback(parseRowCallback(sqlMap));
		customSql.setDialect(group.getDialect());
		customSql.setGroup(group.getIdentity());
		return customSql;
	}

	public static CustomSql parseCustomSql(Business business, SqlType type) throws ParseException {
		CustomSqlImpl customSql = new CustomSqlImpl();
		switch (type) {
			case Select: {
				customSql.setIdentity(type.name() + "#" + business.getClass().getName());
				break;
			}
			case Insert: {
				break;
			}
			case Update: {
				break;
			}
			case Delete: {

			}
		}
		return customSql;
	}

	@SuppressWarnings("unchecked")
	public static RuntimeSql parseRuntimeSql(CustomSql customSql, Object... parameters) throws ParseException {
		String runtimeSql = customSql.getCustomSql();

		List<Object> runtimeParams = new ArrayList<Object>();
		if (parameters != null && parameters.length > 0) {
			Map<String, Object> valueMap = new HashMap<String, Object>();
			int markCount = 0;
			for (Object parameter : parameters) {
				if (parameter instanceof Map) {
					valueMap.putAll((Map<String, Object>) parameter);
				} else {
					valueMap.put("@" + (markCount++), parameter);
				}
			}
			valueMap.put("mp", new MappingParam(runtimeParams));
			valueMap.put("mn", new MappingName(customSql.getParamAlias()));
			runtimeSql = StringTemplate.process(customSql.getIdentity(), valueMap);
		}
		return new RuntimeSql(runtimeSql, runtimeParams.toArray());
	}

	public static String parseCountSql(String sql) {
		return "select count(*) from (" + sql.trim() + ") as t";
	}

}
